/*-------------------------------------------------------------------------
 *
 * dict_synonym.c
 *		同义词字典：用同义词替换单词
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/tsearch/dict_synonym.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "commands/defrem.h"
#include "tsearch/ts_locale.h"
#include "tsearch/ts_utils.h"
#include "utils/builtins.h"

typedef struct
{
	char	   *in;
	char	   *out;
	int			outlen;
	uint16		flags;
} Syn;

typedef struct
{
	int			len;			/* syn 数组的长度 */
	Syn		   *syn;
	bool		case_sensitive;
} DictSyn;

/*
 * 查找 'in' 字符串中下一个以空格分隔的单词。
 * 返回指向单词第一个字符的指针，以及指向单词最后一个字符后一个字节的指针（在 *end 中）。
 * 如果 flags 为非空，则单词末尾的字符 '*' 不会被视为单词字符。
 */
static char * fc_findwrd(char *fc_in, char **fc_end, uint16 *fc_flags)
{
	char	   *fc_start;
	char	   *fc_lastchar;

	/* 跳过前导空格 */
	while (*fc_in && t_isspace(fc_in))
		fc_in += pg_mblen(fc_in);

	/* 在空行上返回 NULL */
	if (*fc_in == '\0')
	{
		*fc_end = NULL;
		return NULL;
	}

	fc_lastchar = fc_start = fc_in;

	/* 查找单词的结束 */
	while (*fc_in && !t_isspace(fc_in))
	{
		fc_lastchar = fc_in;
		fc_in += pg_mblen(fc_in);
	}

	if (fc_in - fc_lastchar == 1 && t_iseq(fc_lastchar, '*') && fc_flags)
	{
		*fc_flags = TSL_PREFIX;
		*fc_end = fc_lastchar;
	}
	else
	{
		if (fc_flags)
			*fc_flags = 0;
		*fc_end = fc_in;
	}

	return fc_start;
}

static int fc_compareSyn(const void *fc_a, const void *fc_b)
{
	return strcmp(((const Syn *) fc_a)->in, ((const Syn *) fc_b)->in);
}


Datum dsynonym_init(PG_FUNCTION_ARGS)
{
	List	   *fc_dictoptions = (List *) PG_GETARG_POINTER(0);
	DictSyn    *fc_d;
	ListCell   *fc_l;
	char	   *fc_filename = NULL;
	bool		fc_case_sensitive = false;
	tsearch_readline_state fc_trst;
	char	   *fc_starti,
			   *fc_starto,
			   *fc_end = NULL;
	int			fc_cur = 0;
	char	   *fc_line = NULL;
	uint16		fc_flags = 0;

	foreach(fc_l, fc_dictoptions)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_l);

		if (strcmp(fc_defel->defname, "synonyms") == 0)
			fc_filename = defGetString(fc_defel);
		else if (strcmp(fc_defel->defname, "casesensitive") == 0)
			fc_case_sensitive = defGetBoolean(fc_defel);
		else
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("unrecognized synonym parameter: \"%s\"",
							fc_defel->defname)));
	}

	if (!fc_filename)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("missing Synonyms parameter")));

	fc_filename = get_tsearch_config_filename(fc_filename, "syn");

	if (!tsearch_readline_begin(&fc_trst, fc_filename))
		ereport(ERROR,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("could not open synonym file \"%s\": %m",
						fc_filename)));

	fc_d = (DictSyn *) palloc0(sizeof(DictSyn));

	while ((fc_line = tsearch_readline(&fc_trst)) != NULL)
	{
		fc_starti = fc_findwrd(fc_line, &fc_end, NULL);
		if (!fc_starti)
		{
			/* 空行 */
			goto skipline;
		}
		if (*fc_end == '\0')
		{
			/* 仅有一个单词的行。静默忽略。 */
			goto skipline;
		}
		*fc_end = '\0';

		fc_starto = fc_findwrd(fc_end + 1, &fc_end, &fc_flags);
		if (!fc_starto)
		{
			/* 仅有一个单词（+空格）的行。静默忽略。 */
			goto skipline;
		}
		*fc_end = '\0';

		/*
		 * starti 现在指向第一个单词，starto 指向行中的第二个单词，
		 * 两个单词的末尾都有 \0 终止符。
		 */

		if (fc_cur >= fc_d->len)
		{
			if (fc_d->len == 0)
			{
				fc_d->len = 64;
				fc_d->syn = (Syn *) palloc(sizeof(Syn) * fc_d->len);
			}
			else
			{
				fc_d->len *= 2;
				fc_d->syn = (Syn *) repalloc(fc_d->syn, sizeof(Syn) * fc_d->len);
			}
		}

		if (fc_case_sensitive)
		{
			fc_d->syn[fc_cur].in = pstrdup(fc_starti);
			fc_d->syn[fc_cur].out = pstrdup(fc_starto);
		}
		else
		{
			fc_d->syn[fc_cur].in = lowerstr(fc_starti);
			fc_d->syn[fc_cur].out = lowerstr(fc_starto);
		}

		fc_d->syn[fc_cur].outlen = strlen(fc_starto);
		fc_d->syn[fc_cur].flags = fc_flags;

		fc_cur++;

skipline:
		pfree(fc_line);
	}

	tsearch_readline_end(&fc_trst);

	fc_d->len = fc_cur;
	qsort(fc_d->syn, fc_d->len, sizeof(Syn), fc_compareSyn);

	fc_d->case_sensitive = fc_case_sensitive;

	PG_RETURN_POINTER(fc_d);
}

Datum dsynonym_lexize(PG_FUNCTION_ARGS)
{
	DictSyn    *fc_d = (DictSyn *) PG_GETARG_POINTER(0);
	char	   *fc_in = (char *) PG_GETARG_POINTER(1);
	int32		fc_len = PG_GETARG_INT32(2);
	Syn			fc_key,
			   *fc_found;
	TSLexeme   *fc_res;

	/* 注意：d->len 测试防止 Solaris bsearch-of-no-items 错误 */
	if (fc_len <= 0 || fc_d->len <= 0)
		PG_RETURN_POINTER(NULL);

	if (fc_d->case_sensitive)
		fc_key.in = pnstrdup(fc_in, fc_len);
	else
		fc_key.in = lowerstr_with_len(fc_in, fc_len);

	fc_key.out = NULL;

	fc_found = (Syn *) bsearch(&fc_key, fc_d->syn, fc_d->len, sizeof(Syn), fc_compareSyn);
	pfree(fc_key.in);

	if (!fc_found)
		PG_RETURN_POINTER(NULL);

	fc_res = palloc0(sizeof(TSLexeme) * 2);
	fc_res[0].lexeme = pnstrdup(fc_found->out, fc_found->outlen);
	fc_res[0].flags = fc_found->flags;

	PG_RETURN_POINTER(fc_res);
}
